/**
 *******************************************************************************
 * @file    MDR12065_flash.c
 * @author  Milandr Application Team
 * @version V0.2.0
 * @date    09/04/2026
 * @brief   This file contains all the FLASH firmware functions.
 *******************************************************************************
 * <br><br>
 *
 * THE PRESENT FIRMWARE IS FOR GUIDANCE ONLY. IT AIMS AT PROVIDING CUSTOMERS
 * WITH CODING INFORMATION REGARDING MILANDR'S PRODUCTS IN ORDER TO FACILITATE
 * THE USE AND SAVE TIME. MILANDR SHALL NOT BE HELD LIABLE FOR ANY
 * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES RESULTING
 * FROM THE CONTENT OF SUCH FIRMWARE AND/OR A USE MADE BY CUSTOMERS OF THE
 * CODING INFORMATION CONTAINED HEREIN IN THEIR PRODUCTS.
 *
 * <h2><center>&copy; COPYRIGHT 2026 Milandr</center></h2>
 *******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/
#include "MDR12065_flash.h"
#include "MDR12065_utils.h"
#include "system_MDR12065_it.h"

/** @addtogroup MDR12065_StdPeriph_Driver MDR12065 Standard Peripheral Driver
 * @{
 */

/** @addtogroup FLASH FLASH
 * @{
 */

/** @defgroup FLASH_Private_Types FLASH Private Types
 * @{
 */

/**
 * @brief FLASH memory banks for mass erase.
 */
typedef enum {
    FLASH_ERASE_BANK_MAIN = ((uint32_t)0x00), /*!< Select the FLASH main bank to erase. */
    FLASH_ERASE_BANK_ALL  = ((uint32_t)0x01)  /*!< Select the FLASH main and information banks to erase. */
} FLASH_EraseMemoryBank_TypeDef;

#define IS_FLASH_ERASE_BANK(BANK) (((BANK) == FLASH_ERASE_BANK_MAIN) || \
                                   ((BANK) == FLASH_ERASE_BANK_ALL))

/** @} */ /* End of the group FLASH_Private_Types */

/** @defgroup FLASH_Private_Defines FLASH Private Defines
 * @{
 */

/**
 * @brief FLASH address.
 */
#define FLASH_ADDR_BYTE_MASK     ((uint32_t)0x3)
#define FLASH_ADDR_HALFWORD_MASK ((uint32_t)0x2)

/**
 * @brief FLASH page address.
 */

#define FLASH_GET_MAIN_PAGE_ADDR(PAGE) ((((PAGE) << FLASH_ADR_MAIN_XADR_PAGE_Pos) & (FLASH_ADR_MAIN_XADR_PAGE_Msk | FLASH_ADR_MAIN_BLOCK_Msk)) | FLASH_BASE)
#define FLASH_GET_INFO_PAGE_ADDR(PAGE) ((((PAGE) << FLASH_ADR_INFO_XADR_PAGE_Pos) & (FLASH_ADR_INFO_XADR_PAGE_Msk | FLASH_ADR_INFO_BLOCK_Msk)) | FLASH_BOOT_BASE)

/**
 * @brief FLASH page number.
 */

#define FLASH_GET_INFO_PAGE_NUMBER(ADDR) (((ADDR) & (FLASH_ADR_INFO_XADR_PAGE_Msk | FLASH_ADR_INFO_BLOCK_Msk)) >> FLASH_ADR_INFO_XADR_PAGE_Pos)
#define IS_FLASH_MAIN_PAGE_NUMBER(PAGE)  (((PAGE) & ~(uint32_t)((FLASH_ADR_MAIN_XADR_PAGE_Msk | FLASH_ADR_MAIN_BLOCK_Msk) >> FLASH_ADR_MAIN_XADR_PAGE_Pos)) == 0)
#define IS_FLASH_INFO_PAGE_NUMBER(PAGE)  (((PAGE) & ~(uint32_t)((FLASH_ADR_INFO_XADR_PAGE_Msk | FLASH_ADR_INFO_BLOCK_Msk) >> FLASH_ADR_INFO_XADR_PAGE_Pos)) == 0)

/**
 * @brief FLASH factory data.
 */
#if defined(USE_FACTORY_DATA_PROTECTION)
#define FLASH_FACTORY_DATA_PAGE_NUMBER_ID217 (3)
#define FLASH_ADDR_INFO_WORD_MASK_ID217      (FLASH_ADR_INFO_BLOCK_Msk | FLASH_ADR_INFO_XADR_Msk | FLASH_ADR_YADR_Msk)
#define FLASH_FACTORY_DATA_START_ADDR_ID217  (MDR_FLASH_SPECIAL_FIELDS_BASE & FLASH_ADDR_INFO_WORD_MASK_ID217)
#define FLASH_FACTORY_DATA_END_ADDR_ID217    ((MDR_FLASH_SPECIAL_FIELDS_BASE & FLASH_ADDR_INFO_WORD_MASK_ID217) + 0x1B)
#endif
/** @} */ /* End of the group FLASH_Private_Defines */

/** @addtogroup FLASH_Private_Functions FLASH Private Functions
 * @{
 */

/** @addtogroup FLASH_Private_Functions_Not_Allowed_to_Be_Located_in_FLASH Functions Not Allowed to Be Located in the FLASH
 * @{
 */

/** @addtogroup FLASH_Private_Register_Access_Functions_Not_Allowed_to_Be_Located_in_FLASH FLASH Register Access Functions
 * @{
 */

#if defined(USE_FACTORY_DATA_PROTECTION)
__RAM_TEXT static uint32_t FLASH_GetInfoPageNumber(uint32_t PageAddress);
#endif

__RAM_TEXT static void               FLASH_MassErase(FLASH_EraseMemoryBank_TypeDef Bank);
__RAM_TEXT __STATIC_FORCEINLINE void FLASH_ProgramCycle(uint32_t Command, uint32_t DelayProg);

#if defined(USE_FACTORY_DATA_PROTECTION)
/**
 * @brief  Get the FLASH page number for the information bank.
 * @param  PageAddress: The FLASH page address.
 * @return The FLASH page number for the selected address.
 */
static uint32_t FLASH_GetInfoPageNumber(uint32_t PageAddress)
{
    uint32_t PageNumber;

    PageNumber = FLASH_GET_INFO_PAGE_NUMBER(PageAddress);

    return PageNumber;
}
#endif

/**
 * @brief  Erase the main bank or the main and the information banks.
 * @param  Bank: @ref FLASH_EraseMemoryBank_TypeDef - select a FLASH bank to erase.
 *         This parameter can be one of the following values:
 *             @arg FLASH_ERASE_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_ERASE_BANK_ALL:  The FLASH main and information banks.
 * @return None.
 */
static void FLASH_MassErase(FLASH_EraseMemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t DelayConst;
    uint32_t Block;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_ERASE_BANK(Bank));

    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_US(SystemCoreClock);

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_ERASE_BANK_ALL) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    for (Block = 0; Block < 2; Block++) {
        MDR_FLASH->ADR = (Block << FLASH_ADR_MAIN_BLOCK_Pos) | (Block << FLASH_ADR_INFO_BLOCK_Pos);

        Command |= FLASH_CMD_XE | FLASH_CMD_MAS1 | FLASH_CMD_ERASE;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVS_US, DelayConst));

        Command |= FLASH_CMD_NVSTR;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TME_US, DelayConst));

        Command &= ~FLASH_CMD_ERASE;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVH1_US, DelayConst));

        Command &= ~(FLASH_CMD_XE | FLASH_CMD_MAS1 | FLASH_CMD_NVSTR);
        MDR_FLASH->CMD = Command;
    }

    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;
    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief  Cycle for programming an 32-bit value.
 *         The initial programming procedure must first be completed.
 * @param  Command: The command value, to which is added the set/clear of PROG2 bit.
 * @param  DelayProg: FLASH_TPROG_US delay time in loops.
 * @return None.
 */
void FLASH_ProgramCycle(uint32_t Command, uint32_t DelayProg)
{
    Command |= FLASH_CMD_YE;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DelayProg);

    Command &= ~FLASH_CMD_YE;
    MDR_FLASH->CMD = Command;
}

/** @} */ /* End of the group FLASH_Private_Register_Access_Functions_Not_Allowed_to_Be_Located_in_FLASH */

/** @} */ /* End of the group FLASH_Private_Functions_Not_Allowed_to_Be_Located_in_FLASH */

/** @} */ /* End of the group FLASH_Private_Functions */

/** @addtogroup FLASH_Exported_Functions FLASH Exported Functions
 * @{
 */

/** @addtogroup FLASH_Exported_Functions_Allowed_to_Be_Located_in_FLASH Functions Allowed to Be Located in the FLASH
 * @{
 */

/** @addtogroup FLASH_Exported_Control_Functions_Allowed_to_Be_Located_in_FLASH FLASH Control Functions
 * @{
 */

/**
 * @brief  Lock/Unlock access to FLASH memory via registers (locked by default).
 * @param  LockState: @ref FlagStatus - a new lock state of the access to FLASH memory via registers.
 *         This parameter must be one of the @ref FlagStatus values:
 *             @arg RESET: Unlock the access;
 *             @arg SET: Lock the access.
 * @return None.
 */
void FLASH_SetRegistersLock(FlagStatus LockState)
{
    /* Check the parameters. */
    assert_param(IS_BIT_STATUS(LockState));

    if (LockState != RESET) {
        /* Lock access if SET. */
        MDR_FLASH->KEY = FLASH_KEY_KEY_LOCK;
    } else {
        /* Unlock access if RESET. */
        MDR_FLASH->KEY = FLASH_KEY_KEY_UNLOCK;
    }
}

/**
 * @brief  Get the current state of access to FLASH memory via registers (locked by default).
 * @param  None.
 * @return @ref FlagStatus - lock state:
 *             @arg RESET: Unlocked;
 *             @arg SET: Locked.
 */
FlagStatus FLASH_GetRegistersLock(void)
{
    if (MDR_FLASH->KEY == FLASH_KEY_KEY_UNLOCK) {
        /* Access to FLASH memory via registers is unlocked. */
        return RESET;
    } else {
        /* Access to FLASH memory via registers is locked. */
        return SET;
    }
}

/** @} */ /* End of the group FLASH_Exported_Control_Functions_Allowed_to_Be_Located_in_FLASH */

/** @addtogroup FLASH_Exported_Special_Fields_Control_Functions_Allowed_to_Be_Located_in_FLASH FLASH Special Fields Control Functions
 * @{
 */

/**
 * @brief   Program protection bits for selected FLASH memory regions.
 * @warning After programming the JTAG interface is blocked.
 * @param   ProtectionRegions: Protection regions.
 *          This parameter must be any combination of @ref FLASH_ProtectionRegions_TypeDef values.
 * @return  None.
 */
void FLASH_ProgramProtection(uint32_t ProtectionRegions)
{
    uint32_t Protection;
    uint32_t ProtectionAddr;

    /* Check the parameters. */
    assert_param(IS_FLASH_PROTECTION_REGIONS(ProtectionRegions));

    ProtectionAddr = (uint32_t)&MDR_FLASH_SPECIAL_FIELDS->PROTECTION;

    Protection = FLASH_SPECIAL_FIELDS_PROTECTION_KEY_UNLOCK | (~ProtectionRegions & FLASH_SPECIAL_FIELDS_PROTECTION_REGIONS_Msk);
    FLASH_ProgramWord(ProtectionAddr, Protection, FLASH_BANK_INFO);
}

/**
 * @brief  Read protection state of the FLASH memory regions.
 * @param  None.
 * @return A combined value of the individual protection states (@ref FLASH_ProtectionRegions_TypeDef).
 */
uint32_t FLASH_ReadProtection(void)
{
    uint32_t Protection;

    Protection = MDR_FLASH_SPECIAL_FIELDS->PROTECTION;

    if ((Protection & FLASH_SPECIAL_FIELDS_PROTECTION_KEY_Msk) == FLASH_SPECIAL_FIELDS_PROTECTION_KEY_UNLOCK) {
        Protection = ~Protection & FLASH_SPECIAL_FIELDS_PROTECTION_REGIONS_Msk;
    } else {
        Protection = 0;
    }

    return Protection;
}

/**
 * @brief  Program factory data (UNIQUE_ID, TRIMs) and checksum (TRIM_CS) into FLASH memory.
 * @note   If USE_FACTORY_DATA_PROTECTION is defined, then factory data programming is not performed.
 * @param  FactoryData: The pointer to the @ref FLASH_FactoryData_TypeDef structure
 *         that contains identification information and trim values.
 * @return None.
 */
void FLASH_ProgramFactoryData(const FLASH_FactoryData_TypeDef* FactoryData)
{
    uint32_t                         TrimWord;
    uint8_t                          TrimCS;
    uint32_t                         Index;
    const uint8_t*                   PtrFactoryData;
    MDR_FLASH_SpecialFields_TypeDef* FlashFactoryData;

    FlashFactoryData = MDR_FLASH_SPECIAL_FIELDS;
    TrimWord         = *((const uint32_t*)&FactoryData->TrimHSI);

    /* Checksum calculation. */
    PtrFactoryData = (const uint8_t*)FactoryData;
    for (TrimCS = 0, Index = 0; Index < 24; Index++) {
        TrimCS += PtrFactoryData[Index];
    }

    /* Program UNIQUE_ID, TRIMs and checksum (TRIM_CS) into FLASH memory. */
    for (Index = 0; Index < 5; Index++) {
        FLASH_ProgramWord((uint32_t)&FlashFactoryData->UNIQUE_ID[Index], FactoryData->UniqueID[Index], FLASH_BANK_INFO);
    }
    FLASH_ProgramWord((uint32_t)&FlashFactoryData->TRIM_HSI, TrimWord, FLASH_BANK_INFO);
    FLASH_ProgramByte((uint32_t)&FlashFactoryData->TRIM_CS, TrimCS, FLASH_BANK_INFO);
}

/**
 * @brief  Read factory data (UNIQUE_ID, TRIMs) from FLASH memory and verify the checksum (TRIM_CS).
 * @param  FactoryData: The pointer to the @ref FLASH_FactoryData_TypeDef structure
 *         where identification information and trim values should be stored.
 * @return @ref ErrorStatus - checksum verification result.
 */
ErrorStatus FLASH_ReadFactoryData(FLASH_FactoryData_TypeDef* FactoryData)
{
    uint8_t                          ReadTrimCS;
    uint8_t                          CalcTrimCS;
    uint32_t                         Index;
    uint8_t*                         PtrFactoryData;
    MDR_FLASH_SpecialFields_TypeDef* FlashFactoryData;

    FlashFactoryData = MDR_FLASH_SPECIAL_FIELDS;

    /* Read UNIQUE_ID, TRIMs and checksum (TRIM_CS) from FLASH memory. */
    for (Index = 0; Index < 5; Index++) {
        FactoryData->UniqueID[Index] = FlashFactoryData->UNIQUE_ID[Index];
    }
    *((uint32_t*)&FactoryData->TrimHSI) = *((uint32_t*)&FlashFactoryData->TRIM_HSI);
    ReadTrimCS                          = FlashFactoryData->TRIM_CS;

    /* Checksum calculation. */
    PtrFactoryData = (uint8_t*)FactoryData;
    for (CalcTrimCS = 0, Index = 0; Index < 24; Index++) {
        CalcTrimCS += PtrFactoryData[Index];
    }

    /* Comparison of checksums. */
    if (ReadTrimCS != CalcTrimCS) {
        return ERROR;
    } else {
        return SUCCESS;
    }
}

/** @} */ /* End of the group FLASH_Exported_Special_Fields_Control_Functions_Allowed_to_Be_Located_in_FLASH */

/** @addtogroup FLASH_Exported_JTAG_Locking_Functions_Allowed_to_Be_Located_in_FLASH FLASH JTAG Locking Functions
 * @{
 */

/**
 * @brief  Lock/Unlock write access to the CHIP_ID_CTRL register that performs JTAG blocking.
 *         The CHIP_ID_CTRL register are locked by default.
 * @param  LockState: @ref FlagStatus - a new lock state of the CHIP_ID_CTRL register.
 *         This parameter must be one of the @ref FlagStatus values:
 *             @arg RESET: Unlock the write access;
 *             @arg SET: Lock the write access.
 * @return None.
 */
void FLASH_JTAG_SetRegistersLock(FlagStatus LockState)
{
    /* Check the parameters. */
    assert_param(IS_BIT_STATUS(LockState));

    if (LockState != RESET) {
        /* Lock write access if SET. */
        MDR_FLASH->OTP_KEY = FLASH_OTP_KEY_OTP_KEY_LOCK;
    } else {
        /* Unlock write access if RESET. */
        MDR_FLASH->OTP_KEY = FLASH_OTP_KEY_OTP_KEY_UNLOCK;
    }
}

/**
 * @brief  Get the current CHIP_ID_CTRL register write access lock state.
 *         The CHIP_ID_CTRL register are locked by default.
 * @param  None.
 * @return @ref FlagStatus - write lock state:
 *             @arg RESET: Unlocked;
 *             @arg SET: Locked.
 */
FlagStatus FLASH_JTAG_GetRegistersLock(void)
{
    if (MDR_FLASH->OTP_KEY == FLASH_OTP_KEY_OTP_KEY_UNLOCK) {
        /* CHIP_ID_CTRL write access is unlocked. */
        return RESET;
    } else {
        /* CHIP_ID_CTRL write access is locked. */
        return SET;
    }
}

/**
 * @brief   Lock the JTAG interface permanently by programming the OTP memory.
 * @warning After programming the OTP memory, the JTAG interface will be locked forever.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_JTAG_SetLockForever(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   None.
 * @return  None.
 */
void FLASH_JTAG_SetLockForever(void)
{
    uint32_t Iteration;
    uint32_t OTPProg;
    uint32_t DelayConst;
    uint32_t MStatus;

    OTPProg    = FLASH_CHIP_ID_CTRL_OTP_PROG;
    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_MS(SystemCoreClock);

    MStatus                 = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    MDR_FLASH->CHIP_ID_CTRL = OTPProg;
    for (Iteration = 0; Iteration < 5; Iteration++) {
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_MS(1, DelayConst));
        OTPProg ^= FLASH_CHIP_ID_CTRL_OTP_PROG;
        MDR_FLASH->CHIP_ID_CTRL = OTPProg;
    }
    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief  Get the JTAG interface lock status.
 * @note   Available in microcontrollers since rev. 2.0.
 * @param  None.
 * @return @ref FlagStatus - JTAG interface lock status:
 *             @arg RESET: Unlocked;
 *             @arg SET: Locked.
 */
FlagStatus FLASH_JTAG_GetLockStatus(void)
{
    FlagStatus Status;

    if (MDR_FLASH->CHIP_ID_CTRL & FLASH_CHIP_ID_CTRL_OTPTST2) {
        Status = SET;
    } else {
        Status = RESET;
    }

    return Status;
}

/** @} */ /* End of the group FLASH_Exported_JTAG_Locking_Functions_Allowed_to_Be_Located_in_FLASH */

/** @addtogroup FLASH_Exported_Register_Access_Functions_Allowed_to_Be_Located_in_FLASH FLASH Register Access Functions
 * @{
 */

/**
 * @brief  Get the FLASH page address (can be used for FLASH_ErasePage()).
 * @param  PageNumber: The FLASH page number.
 *         This parameter is a value:
 *         - main bank: from 0 to 127,
 *         - information bank: from 0 to 3;
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return The address of selected FLASH page.
 */
uint32_t FLASH_GetPageAddress(uint32_t PageNumber, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t PageAddress;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));
    assert_param((Bank == FLASH_BANK_MAIN) ? IS_FLASH_MAIN_PAGE_NUMBER(PageNumber) : IS_FLASH_INFO_PAGE_NUMBER(PageNumber));

    if (Bank == FLASH_BANK_MAIN) {
        PageAddress = FLASH_GET_MAIN_PAGE_ADDR(PageNumber);
    } else {
        PageAddress = FLASH_GET_INFO_PAGE_ADDR(PageNumber);
    }

    return PageAddress;
}

/** @} */ /* End of the group FLASH_Exported_Register_Access_Functions_Allowed_to_Be_Located_in_FLASH */

/** @} */ /* End of the group FLASH_Exported_Functions_Allowed_to_Be_Located_in_FLASH */

/** @addtogroup FLASH_Exported_Functions_Not_Allowed_to_Be_Located_in_FLASH Functions Not Allowed to Be Located in the FLASH
 * @{
 */

/** @addtogroup FLASH_Exported_Control_Functions_Not_Allowed_to_Be_Located_in_FLASH FLASH Control Functions
 * @{
 */

/**
 * @brief  Set a FLASH latency value. Unlocking FLASH is not required before calling this function.
 * @param  Latency: @ref FLASH_Latency_TypeDef - specify a FLASH latency value in cycles.
 * @return None.
 */
void FLASH_SetLatency(FLASH_Latency_TypeDef Latency)
{
    /* Check the parameters. */
    assert_param(IS_FLASH_LATENCY(Latency));

    /* Set the new latency value. */
    MDR_FLASH->CMD = (uint32_t)Latency | FLASH_CMD_TMR;
}

/**
 * @brief  Get a FLASH latency value. Unlocking FLASH is not required before calling this function.
 * @param  None.
 * @return @ref FLASH_Latency_TypeDef - a FLASH latency value in cycles.
 */
FLASH_Latency_TypeDef FLASH_GetLatency(void)
{
    /* Return the current latency value. */
    return (FLASH_Latency_TypeDef)(MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk);
}

/** @} */ /* End of the group FLASH_Exported_Control_Functions_Not_Allowed_to_Be_Located_in_FLASH */

/** @addtogroup FLASH_Exported_Register_Access_Functions_Not_Allowed_to_Be_Located_in_FLASH FLASH Register Access Functions
 * @{
 */

/**
 * @brief  Read a 16-bit FLASH memory value.
 * @note   Hardware supports only 32-bit word reading.
 * @param  Address: The FLASH memory half word address (two bytes aligned).
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return The selected FLASH memory halfword.
 */
uint16_t FLASH_ReadHalfWord(uint32_t Address, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Data;
    uint32_t Shift;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));
    assert_param(IS_FLASH_ADDR_TWO_BYTE_ALIGNED(Address));

    Data = FLASH_ReadWord(Address & ~FLASH_ADDR_FOUR_BYTE_ALIGN_MASK, Bank);

    Shift = (Address & FLASH_ADDR_HALFWORD_MASK) * 8;
    Data >>= Shift;

    return (uint16_t)Data;
}

/**
 * @brief  Read an 8-bit FLASH memory value.
 * @note   Hardware supports only 32-bit word reading.
 * @param  Address: The FLASH memory byte address.
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The main FLASH;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return The selected FLASH memory byte.
 */
uint8_t FLASH_ReadByte(uint32_t Address, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Data;
    uint32_t Shift;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    Data = FLASH_ReadWord(Address & ~FLASH_ADDR_FOUR_BYTE_ALIGN_MASK, Bank);

    Shift = (Address & FLASH_ADDR_BYTE_MASK) * 8;
    Data >>= Shift;

    return (uint8_t)Data;
}

/**
 * @brief  Read a 32-bit FLASH memory value.
 * @param  Address: A FLASH memory word address (four bytes aligned).
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return The selected FLASH memory word.
 */
uint32_t FLASH_ReadWord(uint32_t Address, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t Data;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));
    assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(Address));

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;
    MDR_FLASH->ADR = Address;
    MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE;
    MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE | FLASH_CMD_SE;
    MDR_FLASH->DO; /* Idle reading for delay. */
    MDR_FLASH->DO; /* Idle reading for delay. */
    Data           = MDR_FLASH->DO;
    Command        = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;
    MDR_FLASH->CMD = Command;

    CSR_Write(CSR_MSTATUS, MStatus);

    return Data;
}

/**
 * @brief  Read the array of the 32-bit FLASH memory values in the packet mode.
 * @param  Address: A FLASH memory word address from which the array will be read (four bytes aligned).
 * @param  PtrDataArray: The pointer to the array where the words read from the FLASH MEMORY will be stored.
 * @param  ArraySize: The size of data array.
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return None.
 */
void FLASH_ReadWordBurst(uint32_t Address, uint32_t* PtrDataArray, uint32_t ArraySize, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t Index;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(Address));
    assert_param(ArraySize > 0);
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    for (Index = 0; Index < ArraySize; Index++) {
        MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE;
        MDR_FLASH->ADR = Address + Index * 4;
        MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE | FLASH_CMD_SE;
        MDR_FLASH->DO; /* Idle reading for delay. */
        MDR_FLASH->DO; /* Idle reading for delay. */
        PtrDataArray[Index] = MDR_FLASH->DO;
    }
    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;

    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief  Read the array of the 32-bit FLASH memory values at specified addresses from the array in the packet mode.
 * @param  PtrAddressArray: The pointer to the array with FLASH memory words addresses to read (four bytes aligned).
 * @param  PtrDataArray: The pointer to the array where the words read from the FLASH MEMORY will be stored.
 * @param  ArraySize: The size of address and data arrays.
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return None.
 */
void FLASH_ReadWordArrayBurst(const uint32_t* PtrAddressArray, uint32_t* PtrDataArray, uint32_t ArraySize, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t Index;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));
    assert_param(ArraySize > 0);
#if (USE_ASSERT_INFO != 0)
    for (Index = 0; Index < ArraySize; Index++) {
        assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(PtrAddressArray[Index]));
    }
#endif

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    for (Index = 0; Index < ArraySize; Index++) {
        MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE;
        MDR_FLASH->ADR = PtrAddressArray[Index];
        MDR_FLASH->CMD = Command | FLASH_CMD_XE | FLASH_CMD_YE | FLASH_CMD_SE;
        MDR_FLASH->DO; /* Idle reading for delay. */
        MDR_FLASH->DO; /* Idle reading for delay. */
        PtrDataArray[Index] = MDR_FLASH->DO;
    }
    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;

    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief  Erase one page (4 KByte) of a selected FLASH memory bank.
 * @note   The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *         Before calling FLASH_ErasePage(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param  Address: Page address in the FLASH memory.
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return None.
 */
void FLASH_ErasePage(uint32_t Address, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t DelayConst;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

#if defined(USE_FACTORY_DATA_PROTECTION)
    if (Bank == FLASH_BANK_INFO) {
        if (FLASH_GetInfoPageNumber(Address) == FLASH_FACTORY_DATA_PAGE_NUMBER_ID217) {
            return;
        }
    }
#endif

    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_US(SystemCoreClock);

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    MDR_FLASH->ADR = Address;
    Command |= FLASH_CMD_XE | FLASH_CMD_ERASE;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVS_US, DelayConst));

    Command |= FLASH_CMD_NVSTR;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TERASE_US, DelayConst));

    Command &= ~FLASH_CMD_ERASE;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVH_US, DelayConst));

    Command &= ~(FLASH_CMD_XE | FLASH_CMD_NVSTR);
    MDR_FLASH->CMD = Command;
    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;
    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief  Erase all pages of a selected FLASH memory bank.
 * @param  Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank.
 *         This parameter can be one of the following values:
 *             @arg FLASH_BANK_MAIN: The FLASH main bank;
 *             @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return None.
 */
void FLASH_EraseAllPages(FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Page;
    uint32_t NumOfPages = 4;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    if (Bank == FLASH_BANK_MAIN) {
        FLASH_MassErase(FLASH_ERASE_BANK_MAIN);
    } else { /* (Bank == FLASH_BANK_INFO) */
        for (Page = 0; Page < NumOfPages; Page++) {
            FLASH_ErasePage(Page << FLASH_ADR_INFO_XADR_PAGE_Pos, FLASH_BANK_INFO);
        }
    }
}

/**
 * @brief  Erase the main and the information banks.
 * @param  None.
 * @return None.
 */
void FLASH_EraseAllBanks(void)
{
#if defined(USE_FACTORY_DATA_PROTECTION)
    FLASH_MassErase(FLASH_ERASE_BANK_MAIN);
    FLASH_EraseAllPages(FLASH_BANK_INFO);
#else
    FLASH_MassErase(FLASH_ERASE_BANK_ALL);
#endif
}

/**
 * @brief   Program a 32-bit FLASH memory value.
 * @warning To count the correct delay times, the core clock must be at least 8 MHz.
 * @warning The same address (32-bit data) can't be programmed more than twice before next erase.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_ProgramWord(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   Address: A FLASH memory word address (four bytes aligned).
 * @param   Data: 32-bit data to be programmed.
 * @param   Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *          This parameter can be one of the following values:
 *              @arg FLASH_BANK_MAIN: The FLASH main bank;
 *              @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return  None.
 */
void FLASH_ProgramWord(uint32_t Address, uint32_t Data, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t DelayConst;
    uint32_t DelayProg;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(Address));
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

#if defined(USE_FACTORY_DATA_PROTECTION)
    if (Bank == FLASH_BANK_INFO) {
        if (((Address & FLASH_ADDR_INFO_WORD_MASK_ID217) >= FLASH_FACTORY_DATA_START_ADDR_ID217) &&
            ((Address & FLASH_ADDR_INFO_WORD_MASK_ID217) <= FLASH_FACTORY_DATA_END_ADDR_ID217)) {
            return;
        }
    }
#endif

    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_US(SystemCoreClock);
    DelayProg  = DELAY_GET_TICKS_US(FLASH_TPROG_US, DelayConst);

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    MDR_FLASH->ADR = Address;
    MDR_FLASH->DI  = Data;

    Command |= FLASH_CMD_XE | FLASH_CMD_PROG;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVS_US, DelayConst));

    Command |= FLASH_CMD_NVSTR;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TPGS_US, DelayConst));

    FLASH_ProgramCycle(Command, DelayProg);

    Command &= ~FLASH_CMD_PROG;
    MDR_FLASH->CMD = Command;
    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVH_US, DelayConst));

    Command &= ~(FLASH_CMD_XE | FLASH_CMD_NVSTR);
    MDR_FLASH->CMD = Command;
    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;

    DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TRCV_US, DelayConst));
    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief   Program a 16-bit FLASH memory value.
 * @warning To count the correct delay times, the core clock must be at least 8 MHz.
 * @warning The same address (32-bit data) can't be programmed more than twice before next erase.
 * @note    Hardware supports only 32-bit word programming.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_ProgramHalfWord(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   Address: A FLASH memory half word address (two bytes aligned).
 * @param   Data: 16-bit data to be programmed.
 * @param   Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *          This parameter can be one of the following values:
 *              @arg FLASH_BANK_MAIN: The FLASH main bank;
 *              @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return  None.
 */
void FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Mask;
    uint32_t Tmp;
    uint32_t Shift;
    uint32_t ProgData;

    /* Check the parameters. */
    assert_param(IS_FLASH_ADDR_TWO_BYTE_ALIGNED(Address));
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    Shift    = (Address & FLASH_ADDR_HALFWORD_MASK) * 8;
    ProgData = (uint32_t)Data << Shift;
    Mask     = (uint32_t)0xFFFF << Shift;
    Address &= ~FLASH_ADDR_FOUR_BYTE_ALIGN_MASK;

    Tmp      = FLASH_ReadWord(Address, Bank);
    ProgData = (Tmp & ~Mask) | (ProgData & Mask);
    FLASH_ProgramWord(Address, ProgData, Bank);
}

/**
 * @brief   Program an 8-bit FLASH memory value.
 * @warning To count the correct delay times, the core clock must be at least 8 MHz.
 * @warning The same address (32-bit data) can't be programmed more than twice before next erase.
 * @note    Hardware supports only 32-bit word programming.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_ProgramByte(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   Address: A FLASH memory byte address.
 * @param   Data: 8-bit data to be programmed.
 * @param   Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *          This parameter can be one of the following values:
 *              @arg FLASH_BANK_MAIN: The FLASH main bank;
 *              @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return  None.
 */
void FLASH_ProgramByte(uint32_t Address, uint8_t Data, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Mask;
    uint32_t Tmp;
    uint32_t Shift;
    uint32_t ProgData;

    /* Check the parameters. */
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    Shift    = (Address & FLASH_ADDR_BYTE_MASK) * 8;
    ProgData = (uint32_t)Data << Shift;
    Mask     = (uint32_t)0xFF << Shift;
    Address &= ~FLASH_ADDR_FOUR_BYTE_ALIGN_MASK;

    Tmp      = FLASH_ReadWord(Address, Bank);
    ProgData = (Tmp & ~Mask) | (ProgData & Mask);
    FLASH_ProgramWord(Address, ProgData, Bank);
}

/**
 * @brief   Program an array of the 32-bit FLASH memory values in the packet mode.
 * @warning To count the correct delay times, the core clock must be at least 8 MHz.
 * @warning The same address (32-bit data) can't be programmed more than twice before next erase.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_ProgramWordBurst(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   Address: A FLASH memory word address from which the array will be programmed (four bytes aligned).
 * @param   PtrDataArray: The pointer to the data array to program.
 * @param   ArraySize: The size of data array.
 * @param   Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *          This parameter can be one of the following values:
 *              @arg FLASH_BANK_MAIN: The FLASH main bank;
 *              @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return  None.
 */
void FLASH_ProgramWordBurst(uint32_t Address, const uint32_t* PtrDataArray, uint32_t ArraySize, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t DelayConst;
    uint32_t DelayProg;
    uint32_t Index;
    uint32_t MStatus;

    /* Check the parameters. */
    assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(Address));
    assert_param(ArraySize > 0);
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_US(SystemCoreClock);
    DelayProg  = DELAY_GET_TICKS_US(FLASH_TPROG_US, DelayConst);

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    Index = 0;
    while (Index < ArraySize) {
#if defined(USE_FACTORY_DATA_PROTECTION)
        if (Bank == FLASH_BANK_INFO) {
            if ((((Address + Index * 4) & FLASH_ADDR_INFO_WORD_MASK_ID217) >= FLASH_FACTORY_DATA_START_ADDR_ID217) &&
                (((Address + Index * 4) & FLASH_ADDR_INFO_WORD_MASK_ID217) <= FLASH_FACTORY_DATA_END_ADDR_ID217)) {
                Index++;
                continue;
            }
        }
#endif
        MDR_FLASH->ADR = Address + Index * 4;
        MDR_FLASH->DI  = PtrDataArray[Index];

        Command |= FLASH_CMD_XE | FLASH_CMD_PROG;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVS_US, DelayConst));

        Command |= FLASH_CMD_NVSTR;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TPGS_US, DelayConst));

        FLASH_ProgramCycle(Command, DelayProg);
        Index++;
        /* Can be programmed with reduced delays (without 30 us delay) if only YADR is changed. */
        while ((Index < ArraySize) && (((Address + (Index - 1) * 4) & ~FLASH_ADR_YADR_Msk) == ((Address + Index * 4) & ~FLASH_ADR_YADR_Msk))) {
#if defined(USE_FACTORY_DATA_PROTECTION)
            if (Bank == FLASH_BANK_INFO) {
                if ((((Address + Index * 4) & FLASH_ADDR_INFO_WORD_MASK_ID217) >= FLASH_FACTORY_DATA_START_ADDR_ID217) &&
                    (((Address + Index * 4) & FLASH_ADDR_INFO_WORD_MASK_ID217) <= FLASH_FACTORY_DATA_END_ADDR_ID217)) {
                    Index++;
                    continue;
                }
            }
#endif
            MDR_FLASH->ADR = Address + Index * 4;
            MDR_FLASH->DI  = PtrDataArray[Index];
            FLASH_ProgramCycle(Command, DelayProg);
            Index++;
        }
        Command &= ~FLASH_CMD_PROG;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVH_US, DelayConst));

        Command &= ~(FLASH_CMD_XE | FLASH_CMD_NVSTR);
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TRCV_US, DelayConst));
    }

    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;
    CSR_Write(CSR_MSTATUS, MStatus);
}

/**
 * @brief   Program an array of the 32-bit FLASH memory values at specified addresses from the array in the packet mode.
 * @warning To count the correct delay times, the core clock must be at least 8 MHz.
 * @warning The same address (32-bit data) can't be programmed more than twice before next erase.
 * @note    The delay uses the cycle counter and core clock from SystemCoreClock. Do not disable the cycle counter.
 *          Before calling FLASH_ProgramWordArrayBurst(), you must update SystemCoreClock by calling SystemCoreClockUpdate().
 * @param   PtrAddressArray: The pointer to the array with FLASH memory words addresses to write (four bytes aligned).
 * @param   PtrDataArray: The pointer to the data array to program.
 * @param   ArraySize: The size of address and data arrays.
 * @param   Bank: @ref FLASH_MemoryBank_TypeDef - select a FLASH bank (the main bank or the information bank).
 *          This parameter can be one of the following values:
 *              @arg FLASH_BANK_MAIN: The FLASH main bank;
 *              @arg FLASH_BANK_INFO: The FLASH information bank.
 * @return  None.
 */
void FLASH_ProgramWordArrayBurst(const uint32_t* PtrAddressArray, const uint32_t* PtrDataArray, uint32_t ArraySize, FLASH_MemoryBank_TypeDef Bank)
{
    uint32_t Command;
    uint32_t DelayConst;
    uint32_t DelayProg;
    uint32_t Index;
    uint32_t MStatus;

    /* Check the parameters. */
#if (USE_ASSERT_INFO != 0)
    for (Index = 0; Index < ArraySize; Index++) {
        assert_param(IS_FLASH_ADDR_FOUR_BYTE_ALIGNED(PtrAddressArray[Index]));
    }
#endif
    assert_param(ArraySize > 0);
    assert_param(IS_FLASH_MEMORY_BANK(Bank));

    DelayConst = DELAY_CYCLE_COUNTER_GET_CONST_US(SystemCoreClock);
    DelayProg  = DELAY_GET_TICKS_US(FLASH_TPROG_US, DelayConst);

    MStatus = CSR_ReadClearBits(CSR_MSTATUS, CSR_MSTATUS_MIE);
    Command = (MDR_FLASH->CMD & FLASH_CMD_DELAY_Msk) | FLASH_CMD_CON | FLASH_CMD_TMR;
    Command |= (Bank == FLASH_BANK_INFO) ? FLASH_CMD_IFREN : 0;
    MDR_FLASH->CMD = Command;

    Index = 0;
    while (Index < ArraySize) {
#if defined(USE_FACTORY_DATA_PROTECTION)
        if (Bank == FLASH_BANK_INFO) {
            if (((PtrAddressArray[Index] & FLASH_ADDR_INFO_WORD_MASK_ID217) >= FLASH_FACTORY_DATA_START_ADDR_ID217) &&
                ((PtrAddressArray[Index] & FLASH_ADDR_INFO_WORD_MASK_ID217) <= FLASH_FACTORY_DATA_END_ADDR_ID217)) {
                Index++;
                continue;
            }
        }
#endif
        MDR_FLASH->ADR = PtrAddressArray[Index];
        MDR_FLASH->DI  = PtrDataArray[Index];

        Command |= FLASH_CMD_XE | FLASH_CMD_PROG;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVS_US, DelayConst));

        Command |= FLASH_CMD_NVSTR;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TPGS_US, DelayConst));

        FLASH_ProgramCycle(Command, DelayProg);
        Index++;
        /* Can be programmed with reduced delays (without 30 us delay) if only YADR is changed. */
        while ((Index < ArraySize) && ((PtrAddressArray[Index - 1] & ~FLASH_ADR_YADR_Msk) == (PtrAddressArray[Index] & ~FLASH_ADR_YADR_Msk))) {
#if defined(USE_FACTORY_DATA_PROTECTION)
            if (Bank == FLASH_BANK_INFO) {
                if (((PtrAddressArray[Index] & FLASH_ADDR_INFO_WORD_MASK_ID217) >= FLASH_FACTORY_DATA_START_ADDR_ID217) &&
                    ((PtrAddressArray[Index] & FLASH_ADDR_INFO_WORD_MASK_ID217) <= FLASH_FACTORY_DATA_END_ADDR_ID217)) {
                    Index++;
                    continue;
                }
            }
#endif
            MDR_FLASH->ADR = PtrAddressArray[Index];
            MDR_FLASH->DI  = PtrDataArray[Index];
            FLASH_ProgramCycle(Command, DelayProg);
            Index++;
        }
        Command &= ~FLASH_CMD_PROG;
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TNVH_US, DelayConst));

        Command &= ~(FLASH_CMD_XE | FLASH_CMD_NVSTR);
        MDR_FLASH->CMD = Command;
        DELAY_CycleCounter_WaitTicksAsm(DELAY_GET_TICKS_US(FLASH_TRCV_US, DelayConst));
    }

    MDR_FLASH->CMD = (Command & FLASH_CMD_DELAY_Msk) | FLASH_CMD_TMR;
    CSR_Write(CSR_MSTATUS, MStatus);
}

/** @} */ /* End of the group FLASH_Exported_Register_Access_Functions_Not_Allowed_to_Be_Located_in_FLASH */

/** @} */ /* End of the group FLASH_Exported_Functions_Not_Allowed_to_Be_Located_in_FLASH */

/** @} */ /* End of the group FLASH_Exported_Functions */

/** @} */ /* End of the group FLASH */

/** @} */ /* End of the group MDR12065_StdPeriph_Driver */

/*********************** (C) COPYRIGHT 2026 Milandr ****************************
 *
 * END OF FILE MDR12065_flash.c */

